﻿//using System.Globalization;
//using System.Runtime.CompilerServices;
//using FixedPoint;
//using UnityEngine.Assertions;

//namespace System {

//    public struct F64 {

//        private long m_Value;

//        public override readonly int GetHashCode() => m_Value.GetHashCode();

//        public override readonly string ToString() => ToDouble().ToString(CultureInfo.InvariantCulture);

//        public override readonly bool Equals(object obj) {
//            if (obj is F64 other) {
//                return other.m_Value == m_Value;
//            } else if (obj is int i) {
//                return ((F64)i).m_Value == m_Value;
//            } else if (obj is float f) {
//                return ((F64)f).m_Value == m_Value;
//            } else if (obj is double d) {
//                return ((F64)d).m_Value == m_Value;
//            }
//            return false;
//        }

//        public static bool operator ==(F64 left, F64 right) => left.Equals(right);
//        public static bool operator ==(F64 left, int right) => left.m_Value == ((F64)right).m_Value;
//        public static bool operator ==(int left, F64 right) => ((F64)left).m_Value == right.m_Value;
//        public static bool operator ==(F64 left, float right) => left.m_Value == ((F64)right).m_Value;
//        public static bool operator ==(float left, F64 right) => ((F64)left).m_Value == right.m_Value;
//        public static bool operator ==(F64 left, double right) => left.m_Value == ((F64)right).m_Value;
//        public static bool operator ==(double left, F64 right) => ((F64)left).m_Value == right.m_Value;

//        public static bool operator !=(F64 left, F64 right) => !left.Equals(right);
//        public static bool operator !=(F64 left, int right) => left.m_Value != ((F64)right).m_Value;
//        public static bool operator !=(int left, F64 right) => ((F64)left).m_Value != right.m_Value;
//        public static bool operator !=(F64 left, float right) => left.m_Value != ((F64)right).m_Value;
//        public static bool operator !=(float left, F64 right) => ((F64)left).m_Value != right.m_Value;
//        public static bool operator !=(F64 left, double right) => left.m_Value != ((F64)right).m_Value;
//        public static bool operator !=(double left, F64 right) => ((F64)left).m_Value != right.m_Value;

//        public static bool operator <(F64 left, F64 right) => left.m_Value < right.m_Value;
//        public static bool operator <(int left, F64 right) => ((F64)left).m_Value < right.m_Value;
//        public static bool operator <(F64 left, int right) => left.m_Value < ((F64)right).m_Value;
//        public static bool operator <(float left, F64 right) => ((F64)left).m_Value < right.m_Value;
//        public static bool operator <(F64 left, float right) => left.m_Value < ((F64)right).m_Value;
//        public static bool operator <(double left, F64 right) => ((F64)left).m_Value < right.m_Value;
//        public static bool operator <(F64 left, double right) => left.m_Value < ((F64)right).m_Value;

//        public static bool operator <=(F64 left, F64 right) => left.m_Value <= right.m_Value;
//        public static bool operator <=(int left, F64 right) => ((F64)left).m_Value <= right.m_Value;
//        public static bool operator <=(F64 left, int right) => left.m_Value <= ((F64)right).m_Value;
//        public static bool operator <=(float left, F64 right) => ((F64)left).m_Value <= right.m_Value;
//        public static bool operator <=(F64 left, float right) => left.m_Value <= ((F64)right).m_Value;
//        public static bool operator <=(double left, F64 right) => ((F64)left).m_Value <= right.m_Value;
//        public static bool operator <=(F64 left, double right) => left.m_Value <= ((F64)right).m_Value;

//        public static bool operator >(F64 left, F64 right) => left.m_Value > right.m_Value;
//        public static bool operator >(int left, F64 right) => ((F64)left).m_Value > right.m_Value;
//        public static bool operator >(F64 left, int right) => left.m_Value > ((F64)right).m_Value;
//        public static bool operator >(float left, F64 right) => ((F64)left).m_Value > right.m_Value;
//        public static bool operator >(F64 left, float right) => left.m_Value > ((F64)right).m_Value;
//        public static bool operator >(double left, F64 right) => ((F64)left).m_Value > right.m_Value;
//        public static bool operator >(F64 left, double right) => left.m_Value > ((F64)right).m_Value;

//        public static bool operator >=(F64 left, F64 right) => left.m_Value >= right.m_Value;
//        public static bool operator >=(int left, F64 right) => ((F64)left).m_Value >= right.m_Value;
//        public static bool operator >=(F64 left, int right) => left.m_Value >= ((F64)right).m_Value;
//        public static bool operator >=(float left, F64 right) => ((F64)left).m_Value >= right.m_Value;
//        public static bool operator >=(F64 left, float right) => left.m_Value >= ((F64)right).m_Value;
//        public static bool operator >=(double left, F64 right) => ((F64)left).m_Value >= right.m_Value;
//        public static bool operator >=(F64 left, double right) => left.m_Value >= ((F64)right).m_Value;

//        public const int Shift = 32;

//        public const long FractionMask = (1L << Shift) - 1; // Space before 1L needed because of hacky C++ code generator
//        public const long IntegerMask = ~FractionMask;

//        // Constants
//        public static readonly F64 Zero = new() { m_Value = 0L };
//        public static readonly F64 Neg1 = new() { m_Value = -1L << Shift };
//        public static readonly F64 One = new() { m_Value = 1L << Shift };
//        public static readonly F64 Two = new() { m_Value = 2L << Shift };
//        public static readonly F64 Three = new() { m_Value = 3L << Shift };
//        public static readonly F64 Four = new() { m_Value = 4L << Shift };
//        public static readonly F64 Half = new() { m_Value = One.m_Value >> 1 };
//        public static readonly F64 Pi = new() { m_Value = 13493037705L }; //(long)(Math.PI * 65536.0) << 16;
//        public static readonly F64 Pi2 = new() { m_Value = 26986075409L };
//        public static readonly F64 PiHalf = new() { m_Value = 6746518852L };
//        public static readonly F64 E = new() { m_Value = 11674931555L };

//        public static readonly F64 MinValue = new() { m_Value = -9223372036854775808L };
//        public static readonly F64 MaxValue = new() { m_Value = 9223372036854775807L };

//        // Private constants
//        private const long RCP_LN2 = 0x171547652L; // 1.0 / log(2.0) ~= 1.4426950408889634
//        private const long RCP_LOG2_E = 2977044471L;  // 1.0 / log2(e) ~= 0.6931471805599453
//        private const int RCP_HALF_PI = 683565276; // 1.0 / (4.0 * 0.5 * Math.PI);  // the 4.0 factor converts directly to s2.30

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static int Nlz(ulong x) {
//            int n = 0;
//            if (x <= 0x00000000FFFFFFFFL) { n += 32; x <<= 32; }
//            if (x <= 0x0000FFFFFFFFFFFFL) { n += 16; x <<= 16; }
//            if (x <= 0x00FFFFFFFFFFFFFFL) { n += 8; x <<= 8; }
//            if (x <= 0x0FFFFFFFFFFFFFFFL) { n += 4; x <<= 4; }
//            if (x <= 0x3FFFFFFFFFFFFFFFL) { n += 2; x <<= 2; }
//            if (x <= 0x7FFFFFFFFFFFFFFFL) { n += 1; }
//            if (x == 0) {
//                return 64;
//            }
//            return n;
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static int MulIntLongLow(int a, long b) {
//            Assert.IsTrue(a >= 0);
//            int bi = (int)(b >> Shift);
//            long bf = b & FractionMask;
//            return (int)FixedUtil.LogicalShiftRight(a * bf, Shift) + a * bi;
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long MulIntLongLong(int a, long b) {
//            Assert.IsTrue(a >= 0);
//            long bi = b >> Shift;
//            long bf = b & FractionMask;
//            return FixedUtil.LogicalShiftRight(a * bf, Shift) + a * bi;
//        }


//        public static explicit operator F64(int @in) => new() {
//            m_Value = (long)@in << Shift
//        };

//        public static explicit operator F64(float @in) => new() {
//            m_Value = (long)(@in * 4294967296.0f)
//        };

//        public static explicit operator F64(double @in) => new() {
//            m_Value = (long)(@in * 4294967296.0)
//        };

//        public static F64 operator +(F64 a) => a;
//        public static F64 operator +(int a, F64 b) => (F64)a + b;
//        public static F64 operator +(F64 a, int b) => a + (F64)b;
//        public static F64 operator +(float a, F64 b) => (F64)a + b;
//        public static F64 operator +(F64 a, float b) => a + (F64)b;
//        public static F64 operator +(double a, F64 b) => (F64)a + b;
//        public static F64 operator +(F64 a, double b) => a + (F64)b;
//        public static F64 operator +(F64 a, F64 b) => new() {
//            m_Value = a.m_Value + b.m_Value
//        };

//        public static F64 operator -(F64 a) => Zero - a;
//        public static F64 operator -(int a, F64 b) => (F64)a - b;
//        public static F64 operator -(F64 a, int b) => a - (F64)b;
//        public static F64 operator -(float a, F64 b) => (F64)a - b;
//        public static F64 operator -(F64 a, float b) => a - (F64)b;
//        public static F64 operator -(double a, F64 b) => (F64)a - b;
//        public static F64 operator -(F64 a, double b) => a - (F64)b;
//        public static F64 operator -(F64 a, F64 b) => new() {
//            m_Value = a.m_Value - b.m_Value
//        };

//        public static F64 operator *(int a, F64 b) => (F64)a * b;
//        public static F64 operator *(F64 a, int b) => a * (F64)b;
//        public static F64 operator *(float a, F64 b) => (F64)a * b;
//        public static F64 operator *(F64 a, float b) => a * (F64)b;
//        public static F64 operator *(double a, F64 b) => (F64)a * b;
//        public static F64 operator *(F64 a, double b) => a * (F64)b;
//        public static F64 operator *(F64 a, F64 b) => new() {
//            m_Value = Mul(a.m_Value, b.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Mul(long a, long b) {
//            long ai = a >> Shift;
//            long af = (a & FractionMask);
//            long bi = b >> Shift;
//            long bf = (b & FractionMask);
//            return FixedUtil.LogicalShiftRight(af * bf, Shift) + ai * b + af * bi;
//        }

//        public static F64 operator /(int a, F64 b) => (F64)a / b;
//        public static F64 operator /(F64 a, int b) => a / (F64)b;
//        public static F64 operator /(float a, F64 b) => (F64)a / b;
//        public static F64 operator /(F64 a, float b) => a / (F64)b;
//        public static F64 operator /(double a, F64 b) => (F64)a / b;
//        public static F64 operator /(F64 a, double b) => a / (F64)b;
//        public static F64 operator /(F64 a, F64 b) => new() {
//            m_Value = Div(a.m_Value, b.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long DivPrecise(long arg_a, long arg_b) {
//            long sign_dif = arg_a ^ arg_b;

//            const ulong b = 0x100000000L; // Number base (32 bits)
//            ulong abs_arg_a = (ulong)((arg_a < 0) ? -arg_a : arg_a);
//            ulong u1 = abs_arg_a >> 32;
//            ulong u0 = abs_arg_a << 32;
//            ulong v = (ulong)((arg_b < 0) ? -arg_b : arg_b);

//            // Overflow?
//            if (u1 >= v) {
//                //rem = 0;
//                return 0x7fffffffffffffffL;
//            }

//            // Shift amount for norm
//            int s = Nlz(v); // 0 <= s <= 63
//            v <<= s; // Normalize the divisor
//            ulong vn1 = v >> 32; // Break the divisor into two 32-bit digits
//            ulong vn0 = v & 0xffffffffL;

//            ulong un32 = (u1 << s) | (u0 >> (64 - s)) & (ulong)((long)-s >> 63);
//            ulong un10 = u0 << s; // Shift dividend left

//            ulong un1 = un10 >> 32; // Break the right half of dividend into two digits
//            ulong un0 = un10 & 0xffffffffL;

//            // Compute the first quotient digit, q1
//            ulong q1 = un32 / vn1;
//            ulong rhat = un32 - q1 * vn1;
//            do {
//                if ((q1 >= b) || ((q1 * vn0) > (b * rhat + un1))) {
//                    q1--;
//                    rhat += vn1;
//                } else {
//                    break;
//                }
//            } while (rhat < b);

//            ulong un21 = un32 * b + un1 - q1 * v; // Multiply and subtract

//            // Compute the second quotient digit, q0
//            ulong q0 = un21 / vn1;
//            rhat = un21 - q0 * vn1;
//            do {
//                if ((q0 >= b) || ((q0 * vn0) > (b * rhat + un0))) {
//                    q0--;
//                    rhat += vn1;
//                } else {
//                    break;
//                }
//            } while (rhat < b);

//            // Calculate the remainder
//            // ulong r = (un21 * b + un0 - q0 * v) >> s;
//            // rem = (long)r;

//            ulong ret = q1 * b + q0;
//            return (sign_dif < 0) ? -(long)ret : (long)ret;
//        }
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long DivFast(long a, long b) {
//            if (b == long.MinValue || b == 0) {
//                FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(DivFast)}", nameof(b), b);
//                return 0;
//            }

//            // Handle negative values.
//            int sign = (b < 0) ? -1 : 1;
//            b *= sign;

//            // Normalize input into [1.0, 2.0( range (convert to s2.30).
//            int offset = 31 - Nlz((ulong)b);
//            int n = (int)FixedUtil.ShiftRight(b, offset + 2);
//            const int ONE = (1 << 30);
//            Assert.IsTrue(n >= ONE);

//            // Polynomial approximation.
//            int res = FixedUtil.RcpPoly6(n - ONE);

//            // Apply exponent, convert back to s32.32.
//            long y = MulIntLongLong(res, a) << 2;
//            return FixedUtil.ShiftRight(sign * y, offset);
//        }
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Div(long a, long b) {
//            if (b == long.MinValue || b == 0) {
//                FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(Div)}", nameof(b), b);
//                return 0;
//            }

//            // Handle negative values.
//            int sign = (b < 0) ? -1 : 1;
//            b *= sign;

//            // Normalize input into [1.0, 2.0( range (convert to s2.30).
//            int offset = 31 - Nlz((ulong)b);
//            int n = (int)FixedUtil.ShiftRight(b, offset + 2);
//            const int ONE = (1 << 30);
//            Assert.IsTrue(n >= ONE);

//            // Polynomial approximation.
//            int res = FixedUtil.RcpPoly4Lut8(n - ONE);

//            // Apply exponent, convert back to s32.32.
//            long y = MulIntLongLong(res, a) << 2;
//            return FixedUtil.ShiftRight(sign * y, offset);
//        }

//        public static F64 operator %(int a, F64 b) => ((F64)a) % b;
//        public static F64 operator %(F64 a, int b) => a % ((F64)b);
//        public static F64 operator %(float a, F64 b) => ((F64)a) % b;
//        public static F64 operator %(F64 a, float b) => a % ((F64)b);
//        public static F64 operator %(double a, F64 b) => ((F64)a) % b;
//        public static F64 operator %(F64 a, double b) => a % ((F64)b);
//        public static F64 operator %(F64 a, F64 b) => new() {
//            m_Value = Mod(a.m_Value, b.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Mod(long a, long b) {
//            if (b == 0) {
//                FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(Mod)}", nameof(b), b);
//                return 0;
//            }
//            return a % b;
//        }

//        public readonly int CeilToInt() => (int)((m_Value + (One.m_Value - 1)) >> Shift);

//        public readonly int FloorToInt() => (int)(m_Value >> Shift);

//        public readonly int RoundToInt() => (int)((m_Value + Half.m_Value) >> Shift);

//        public readonly double ToDouble() => (double)m_Value * (1.0 / 4294967296.0);

//        public readonly float ToFloat() => (float)m_Value * (1.0f / 4294967296.0f);

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Abs(F64 x) {
//            long mask = x.m_Value >> 63;
//            return new() {
//                m_Value = (x.m_Value + mask) ^ mask
//            };
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Nabs(F64 x) => -Abs(x);

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Ceil(F64 x) => new() {
//            m_Value = (x.m_Value + FractionMask) & IntegerMask
//        };

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Floor(F64 x) => new() {
//            m_Value = x.m_Value & IntegerMask
//        };

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Round(F64 x) => new() {
//            m_Value = (x.m_Value + Half.m_Value) & IntegerMask
//        };

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Fract(F64 x) => new() {
//            m_Value = x.m_Value & FractionMask
//        };

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Min(F64 a, F64 b) => (a.m_Value < b.m_Value) ? a : b;

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Max(F64 a, F64 b) => (a.m_Value > b.m_Value) ? a : b;

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Clamp(F64 a, F64 min, F64 max) => (a.m_Value > max.m_Value) ? max : (a.m_Value < min.m_Value) ? min : a;

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Lerp(F64 a, F64 b, F64 t) => a * t + b * (One - t);

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Sqrt(F64 x) => new() {
//            m_Value = Sqrt(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Sqrt(long x) {
//            // Return 0 for all non-positive values.
//            if (x <= 0) {
//                if (x < 0) {
//                    FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(Sqrt)}", nameof(x), x);
//                }
//                return 0;
//            }

//            // Constants (s2.30).
//            const int ONE = (1 << 30);
//            const int SQRT2 = 1518500249; // sqrt(2.0)

//            // Normalize input into [1.0, 2.0( range (as s2.30).
//            int offset = 31 - Nlz((ulong)x);
//            int n = (int)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);
//            Assert.IsTrue(n >= ONE);
//            int y = FixedUtil.SqrtPoly3Lut8(n - ONE);

//            // Divide offset by 2 (to get sqrt), compute adjust value for odd exponents.
//            int adjust = ((offset & 1) != 0) ? SQRT2 : ONE;
//            offset >>= 1;

//            // Apply exponent, convert back to s32.32.
//            long yr = (long)FixedUtil.Qmul30(adjust, y) << 2;
//            return (offset >= 0) ? (yr << offset) : (yr >> -offset);
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 RSqrt(F64 x) => new() {
//            m_Value = RSqrt(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long RSqrt(long x) {
//            // Return 0 for invalid values
//            if (x <= 0) {
//                FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(RSqrt)}", nameof(x), x);
//                return 0;
//            }

//            // Constants (s2.30).
//            const int ONE = (1 << 30);
//            const int HALF_SQRT2 = 759250125; // 0.5 * sqrt(2.0)

//            // Normalize input into [1.0, 2.0( range (as s2.30).
//            int offset = 31 - Nlz((ulong)x);
//            int n = (int)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);
//            Assert.IsTrue(n >= ONE);
//            int y = FixedUtil.RSqrtPoly3Lut16(n - ONE);

//            // Divide offset by 2 (to get sqrt), compute adjust value for odd exponents.
//            int adjust = ((offset & 1) != 0) ? HALF_SQRT2 : ONE;
//            offset >>= 1;

//            // Apply exponent, convert back to s32.32.
//            long yr = (long)FixedUtil.Qmul30(adjust, y) << 2;
//            return (offset >= 0) ? (yr >> offset) : (yr << -offset);
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Rcp(F64 x) => new() {
//            m_Value = Rcp(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Rcp(long x) {
//            if (x == MinValue || x == 0) {
//                FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(Rcp)}", nameof(x), x);
//                return 0;
//            }

//            // Handle negative values.
//            int sign = (x < 0) ? -1 : 1;
//            x *= sign;

//            // Normalize input into [1.0, 2.0( range (convert to s2.30).
//            int offset = 31 - Nlz((ulong)x);
//            int n = (int)FixedUtil.ShiftRight(x, offset + 2);
//            const int ONE = (1 << 30);
//            Assert.IsTrue(n >= ONE);

//            // Polynomial approximation.
//            int res = FixedUtil.RcpPoly4Lut8(n - ONE);
//            long y = (long)(sign * res) << 2;

//            // Apply exponent, convert back to s32.32.
//            return FixedUtil.ShiftRight(y, offset);
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Exp2(F64 x) => new() {
//            m_Value = Exp2(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Exp2(long x) {
//            // Handle values that would under or overflow.
//            if (x >= 32 * One)
//                return long.MaxValue;
//            if (x <= -32 * One)
//                return 0;

//            // Compute exp2 for fractional part.
//            int k = (int)((x & FractionMask) >> 2);
//            long y = (long)FixedUtil.Exp2Poly5(k) << 2;

//            // Combine integer and fractional result, and convert back to s32.32.
//            int intPart = (int)(x >> Shift);
//            return (intPart >= 0) ? (y << intPart) : (y >> -intPart);
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Exp(F64 x) => new() {
//            m_Value = Exp(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Exp(long x) =>
//            // e^x == 2^(x / ln(2))
//            Exp2(Mul(x, RCP_LN2));

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Log(F64 x) => new() {
//            m_Value = Log(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Log(long x) {
//            // Return 0 for invalid values
//            if (x <= 0) {
//                FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(Log)}", nameof(x), x);
//                return 0;
//            }

//            // Normalize value to range [1.0, 2.0( as s2.30 and extract exponent.
//            const int ONE = (1 << 30);
//            int offset = 31 - Nlz((ulong)x);
//            int n = (int)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);
//            Assert.IsTrue(n >= ONE);
//            long y = (long)FixedUtil.LogPoly5Lut8(n - ONE) << 2;

//            // Combine integer and fractional parts (into s32.32).
//            return (long)offset * RCP_LOG2_E + y;
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Log2(F64 x) => new() {
//            m_Value = Log2(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Log2(long x) {
//            // Return 0 for invalid values
//            if (x <= 0) {
//                FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(Log2)}", nameof(x), x);
//                return 0;
//            }

//            // Normalize value to range [1.0, 2.0( as s2.30 and extract exponent.
//            int offset = 31 - Nlz((ulong)x);
//            int n = (int)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);

//            // Polynomial approximation of mantissa.
//            const int ONE = (1 << 30);
//            Assert.IsTrue(n >= ONE);
//            long y = (long)FixedUtil.Log2Poly4Lut16(n - ONE) << 2;

//            // Combine integer and fractional parts (into s32.32).
//            return ((long)offset << Shift) + y;
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Pow(F64 x, F64 exponent) => new() {
//            m_Value = Pow(x.m_Value, exponent.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Pow(long x, long exponent) {
//            // n^0 == 1
//            if (exponent == 0)
//                return One.m_Value;

//            // Return 0 for invalid values
//            if (x <= 0) {
//                if (x < 0) {
//                    FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(Pow)}", nameof(x), x);
//                }
//                return 0;
//            }

//            return Exp(Mul(exponent, Log(x)));
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Sin(F64 x) => new() {
//            m_Value = Sin(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Sin(long x) {
//            // Map [0, 2pi] to [0, 4] (as s2.30).
//            // This also wraps the values into one period.
//            int z = MulIntLongLow(RCP_HALF_PI, x);

//            // Compute sine and convert to s32.32.
//            return (long)UnitSin(z) << 2;
//        }
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static int UnitSin(int z) {
//            // See: http://www.coranac.com/2009/07/sines/

//            // Handle quadrants 1 and 2 by mirroring the [1, 3] range to [-1, 1] (by calculating 2 - z).
//            // The if condition uses the fact that for the quadrants of interest are 0b01 and 0b10 (top two bits are different).
//            if ((z ^ (z << 1)) < 0)
//                z = (1 << 31) - z;

//            // Now z is in range [-1, 1].
//            const int ONE = (1 << 30);
//            Assert.IsTrue((z >= -ONE) && (z <= ONE));

//            // Polynomial approximation.
//            int zz = FixedUtil.Qmul30(z, z);
//            int res = FixedUtil.Qmul30(FixedUtil.SinPoly4(zz), z);

//            // Return s2.30 value.
//            return res;
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Cos(F64 x) => new() {
//            m_Value = Cos(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Cos(long x) => Sin(x + PiHalf.m_Value);

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Tan(F64 x) => new() {
//            m_Value = Tan(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Tan(long x) {
//            int z = MulIntLongLow(RCP_HALF_PI, x);
//            long sinX = (long)UnitSin(z) << 32;
//            long cosX = (long)UnitSin(z + (1 << 30)) << 32;
//            return Div(sinX, cosX);
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static int Atan2Div(long y, long x) {
//            Assert.IsTrue(y >= 0 && x > 0 && x >= y);

//            // Normalize input into [1.0, 2.0( range (convert to s2.30).
//            const int ONE = (1 << 30);
//            const int HALF = (1 << 29);
//            int offset = 31 - Nlz((ulong)x);
//            int n = (int)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);
//            int k = n - ONE;

//            // Polynomial approximation of reciprocal.
//            int oox = FixedUtil.RcpPoly4Lut8(k);
//            Assert.IsTrue(oox >= HALF && oox <= ONE);

//            // Apply exponent and multiply.
//            long yr = (offset >= 0) ? (y >> offset) : (y << -offset);
//            return FixedUtil.Qmul30((int)(yr >> 2), oox);
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Atan2(F64 y, F64 x) => new() {
//            m_Value = Atan2(y.m_Value, x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Atan2(long y, long x) {
//            // See: https://www.dsprelated.com/showarticle/1052.php

//            if (x == 0) {
//                if (y > 0) {
//                    return PiHalf.m_Value;
//                }
//                if (y < 0) {
//                    return -PiHalf.m_Value;
//                }

//                FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(Atan2)}", $"{nameof(y)}, {nameof(x)}", y, x);
//                return 0;
//            }

//            // \note these round negative numbers slightly
//            long nx = x ^ (x >> 63);
//            long ny = y ^ (y >> 63);
//            long negMask = ((x ^ y) >> 63);

//            if (nx >= ny) {
//                int k = Atan2Div(ny, nx);
//                int z = FixedUtil.AtanPoly5Lut8(k);
//                long angle = negMask ^ ((long)z << 2);
//                if (x > 0) {
//                    return angle;
//                }
//                if (y >= 0) {
//                    return angle + Pi.m_Value;
//                }
//                return angle - Pi.m_Value;
//            } else {
//                int k = Atan2Div(nx, ny);
//                int z = FixedUtil.AtanPoly5Lut8(k);
//                long angle = negMask ^ ((long)z << 2);
//                return ((y > 0) ? PiHalf.m_Value : -PiHalf.m_Value) - angle;
//            }
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Asin(F64 x) => new() {
//            m_Value = Asin(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Asin(long x) {
//            // Return 0 for invalid values
//            if (x < -One || x > One) {
//                FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(Asin)}", nameof(x), x);
//                return 0;
//            }

//            return Atan2(x, Sqrt(Mul(One.m_Value + x, One.m_Value - x)));
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Acos(F64 x) => new() {
//            m_Value = Acos(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Acos(long x) {
//            // Return 0 for invalid values
//            if (x < -One || x > One) {
//                FixedUtil.InvalidArgument($"{nameof(F64)}.{nameof(Acos)}", nameof(x), x);
//                return 0;
//            }

//            return Atan2(Sqrt(Mul(One.m_Value + x, One.m_Value - x)), x);
//        }

//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        public static F64 Atan(F64 x) => new() {
//            m_Value = Atan(x.m_Value)
//        };
//        [MethodImpl(FixedUtil.AggressiveInlining)]
//        private static long Atan(long x) {
//            return Atan2(x, One.m_Value);
//        }
//    }
//}